// ReRemind
// Copyright (C) 2009-2010, Avian Waves Software
// SEE LICENSE.TXT for software licensing information

#include "stdafx.h"
#include "LED.h"

// ************************************************************************
// Constants
// ************************************************************************
#define MAX_LOADSTRING MAX_PATH + 1
#define REGISTRY_SETTINGS _T("\\Software\\Avian Waves\\ReRemind")
#define REG_REMINDERKEY _T("System\\State\\Shell\\Reminders")
#define REG_REMINDERVALUE _T("Active")
#define REG_REMINDERKEY65 _T("System\\State\\Reminder\\Count")
#define REG_REMINDERVALUE65a _T("APPT")
#define REG_REMINDERVALUE65b _T("APPTALLDAY")
#define REG_REMINDERVALUE65c _T("TASK")
#define REG_MISSEDCALLKEY _T("System\\State\\Phone")
#define REG_MISSEDCALLVALUE _T("Missed Call Count")
#define REG_VOICEMAILKEY _T("System\\State\\Messages\\vmail\\Total\\Unread")
#define REG_VOICEMAILVALUE _T("Count")
#define REG_SMSKEY _T("System\\State\\Messages\\sms\\Unread")
#define REG_SMSVALUE _T("Count")
#define REG_MMSKEY _T("System\\State\\Messages\\mms\\Unread")
#define REG_MMSVALUE _T("Count")
#define REG_EMAILKEY _T("System\\State\\Messages\\totalemail\\Unread")
#define REG_EMAILVALUE _T("Count")
#define REG_AKUKEY _T("System\\Versions")
#define REG_AKUVALUE _T("Aku")
#define REG_REMINDEROPTIONSKEY _T("ControlPanel\\Notifications\\{15F11F90-8A5F-454c-89FC-BA9B7AAB0CAD}")
#define REG_REMINDEROPTIONSVALUE _T("Options")
#define REG_REMINDERSOUNDVALUE _T("Wave")

// ************************************************************************
// Global variables
// ************************************************************************
HINSTANCE g_hInstance = NULL;
HANDLE g_hThread = NULL;        // Does not get cleaned up in CleanUp() since thread is killed after cleanup
HANDLE g_hEvent = NULL;
HANDLE g_hNotify = NULL;
HREGNOTIFY g_reminderHandlea = NULL;
HREGNOTIFY g_reminderHandleb = NULL;
HREGNOTIFY g_reminderHandlec = NULL;
HREGNOTIFY g_missedCallHandle = NULL;
HREGNOTIFY g_voiceMailHandle = NULL;
HREGNOTIFY g_smsHandle = NULL;
HREGNOTIFY g_mmsHandle = NULL;
HREGNOTIFY g_emailHandle = NULL;
CE_NOTIFICATION_TRIGGER g_trigger;
DWORD g_notifyCount = 0;
wchar_t *g_eventName = L"AvianWavesReRemindNotificationEvent";
wchar_t *g_eventPath = L"\\\\.\\Notifications\\NamedEvents\\AvianWavesReRemindNotificationEvent";
DWORD g_lastAllDayCount = 0;

// Registry settings
DWORD g_notifyInterval = 0;
DWORD g_useInactiveTime = 0;
DWORD g_hourInactiveStart = 0;
DWORD g_hourInactiveEnd = 0;
DWORD g_notifyReminder = 0;
DWORD g_notifyMissedCall = 0;
DWORD g_notifyVoiceMail = 0;
DWORD g_notifySMS = 0;
DWORD g_notifyMMS = 0;
DWORD g_notifyEmail = 0;
DWORD g_notifyWhileOn = 0;
DWORD g_powerOnBeforePlay = 0;
DWORD g_stopNotifyAfter = 0;
DWORD g_vibrate = 0;
DWORD g_vibrateIndex = 1;
DWORD g_allDayWorkaroundHack = 0;
wchar_t g_soundFile[MAX_LOADSTRING];

// ************************************************************************
// Forward declarations of functions included in this code module:
// ************************************************************************
BOOL ReRemindMain(HINSTANCE);
void LoadRegistrySettings();
void CleanUp();
void AddMinutes(SYSTEMTIME *, int);
BOOL IsNotificationReady();
BOOL IsNotificationAllowed();
BOOL IsPowerStateOn();
void NotificationCallBack(HREGNOTIFY, DWORD, const PBYTE, const UINT);
void DestroyReRemindNotifications();
BOOL IsWindows65();
void DoAllDayWorkaroundHack();


// ************************************************************************
// Entry point
// ************************************************************************
BOOL APIENTRY DllMain(HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
  if (g_hInstance == NULL)
  {
    g_hInstance = (HINSTANCE) hModule;
  }

  switch (ul_reason_for_call)
  {
  case DLL_PROCESS_ATTACH:
  case DLL_THREAD_ATTACH:
  case DLL_THREAD_DETACH:
  case DLL_PROCESS_DETACH:
    break;
  }

  return TRUE;
}

// ************************************************************************
// Service Controllers and Cleanup
// ************************************************************************
unsigned long __cdecl MyControllingFunction(LPVOID pParam)
{
  PowerPolicyNotify(PPN_UNATTENDEDMODE, -1); 
  ReRemindMain(g_hInstance);
  CleanUp();

  return 0;
}

void CleanUp()
{
  if (g_hNotify) CeClearUserNotification(g_hNotify);
  if (g_hEvent) CloseHandle(g_hEvent);
  if (g_reminderHandlea) RegistryCloseNotification(g_reminderHandlea);
  if (g_reminderHandleb) RegistryCloseNotification(g_reminderHandleb);
  if (g_reminderHandlec) RegistryCloseNotification(g_reminderHandlec);
  if (g_missedCallHandle) RegistryCloseNotification(g_missedCallHandle);
  if (g_voiceMailHandle) RegistryCloseNotification(g_voiceMailHandle);
  if (g_smsHandle) RegistryCloseNotification(g_smsHandle);
  if (g_mmsHandle) RegistryCloseNotification(g_mmsHandle);
  if (g_emailHandle) RegistryCloseNotification(g_emailHandle);

  DestroyReRemindNotifications();

  g_hInstance = NULL;

  g_hNotify = NULL;
  g_hEvent = NULL;
  g_reminderHandlea = NULL;
  g_reminderHandleb = NULL;
  g_reminderHandlec = NULL;
  g_missedCallHandle = NULL;
  g_voiceMailHandle = NULL;
  g_smsHandle = NULL;
  g_mmsHandle = NULL;
  g_emailHandle = NULL;
  
  PowerPolicyNotify(PPN_UNATTENDEDMODE, 0); 
}



// ************************************************************************
// Service Events
// ************************************************************************
DWORD RER_Close(DWORD dwData)
{
  return 0;
}

DWORD RER_Deinit(DWORD dwData)
{
  CleanUp();

  if (g_hThread) TerminateThread(g_hThread, 1);
  g_hThread = NULL;

  return 1;
}

DWORD RER_Init(DWORD dwData)
{
  g_hThread = CreateThread(0, 0, MyControllingFunction, 0, 0, 0);

  return 1;
}

DWORD RER_IOControl(DWORD dwData, DWORD dwCode, PBYTE pBufIn, DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut)
{
  return 1;
}

DWORD RER_Open(DWORD dwData, DWORD dwAccess, DWORD dwShareMode)
{
  return 0;
}

DWORD RER_Read(DWORD dwData, LPVOID pBuf, DWORD dwLen)
{
  return 0;
}

DWORD RER_Seek(DWORD dwData, long pos, DWORD type)
{
  return 0;
}

DWORD RER_Write(DWORD dwData, LPCVOID pInBuf, DWORD dwInLen)
{
  return 0;
}



// ************************************************************************
// Service Work
// ************************************************************************

BOOL ReRemindMain(HINSTANCE hInstance)
{
  BOOL triggered = FALSE;

  // Set the idle timer (just in case) to get this thing started
  SystemIdleTimerReset();

  // Look for stale notifications
  DestroyReRemindNotifications();

  // Load the initial registry settings
  LoadRegistrySettings();

  // Create event
  g_hEvent = CreateEvent(NULL, FALSE, FALSE, g_eventName);

  // Initial trigger values
  g_trigger.dwSize = sizeof(g_trigger);
  g_trigger.dwType = CNT_TIME;
  g_trigger.dwEvent = NOTIFICATION_EVENT_NONE;
  g_trigger.lpszApplication = g_eventPath;
  g_trigger.lpszArguments = NULL;

  // Initialize count
  g_notifyCount = 0;

  // Register the callbacks
  if (g_notifyReminder || g_allDayWorkaroundHack)
  {
    if (!IsWindows65())
    {
      RegistryNotifyCallback(HKEY_CURRENT_USER, REG_REMINDERKEY, REG_REMINDERVALUE, NotificationCallBack, NULL, NULL, &g_reminderHandlea);
    }
    else
    {
      RegistryNotifyCallback(HKEY_LOCAL_MACHINE, REG_REMINDERKEY65, REG_REMINDERVALUE65a, NotificationCallBack, NULL, NULL, &g_reminderHandlea);
      RegistryNotifyCallback(HKEY_LOCAL_MACHINE, REG_REMINDERKEY65, REG_REMINDERVALUE65b, NotificationCallBack, NULL, NULL, &g_reminderHandleb);
      RegistryNotifyCallback(HKEY_LOCAL_MACHINE, REG_REMINDERKEY65, REG_REMINDERVALUE65c, NotificationCallBack, NULL, NULL, &g_reminderHandlec);
    }
  }

  if (g_notifyMissedCall)
  {
    RegistryNotifyCallback(HKEY_CURRENT_USER, REG_MISSEDCALLKEY, REG_MISSEDCALLVALUE, NotificationCallBack, NULL, NULL, &g_missedCallHandle);
  }

  if (g_notifyVoiceMail)
  {
    RegistryNotifyCallback(HKEY_CURRENT_USER, REG_VOICEMAILKEY, REG_VOICEMAILVALUE, NotificationCallBack, NULL, NULL, &g_voiceMailHandle);
  }

  if (g_notifySMS)
  {
    RegistryNotifyCallback(HKEY_CURRENT_USER, REG_SMSKEY, REG_SMSVALUE, NotificationCallBack, NULL, NULL, &g_smsHandle);
  }

  if (g_notifyMMS)
  {
    RegistryNotifyCallback(HKEY_CURRENT_USER, REG_MMSKEY, REG_MMSVALUE, NotificationCallBack, NULL, NULL, &g_mmsHandle);
  }

  if (g_notifyEmail)
  {
    RegistryNotifyCallback(HKEY_CURRENT_USER, REG_EMAILKEY, REG_EMAILVALUE, NotificationCallBack, NULL, NULL, &g_emailHandle);
  }

  // Check to see if we set an initial notification timer
  NotificationCallBack(NULL, 0, NULL, 0);

  do
  {
    // Reset the triggered flag
    triggered = FALSE;

    // Wait for event
    while (!triggered)
    {
      if(WaitForSingleObject(g_hEvent, INFINITE) == WAIT_OBJECT_0)
      {
        // Notification has fired, so it's gone, set reference to NULL
        g_hNotify = NULL;

        // Give us some time to react
        SystemIdleTimerReset();

        // Check to see if we set a new notification timer
        NotificationCallBack(NULL, 0, NULL, 0);

        // Double check that the notification is still active and we are between valid notification times
        if (IsNotificationReady() && IsNotificationAllowed())
        {
          // Do we power on before playing?
          if (g_powerOnBeforePlay && !IsPowerStateOn())
          {
            SetSystemPowerState(NULL, POWER_STATE_ON, 0);
          }

          // Vibrate if we are supposed to
          if (g_vibrate)
          {
            LedOn((int) g_vibrateIndex);
          }

          // Play the sound!
          SndPlaySync(g_soundFile, 0);

          // Unvibrate
          if (g_vibrate)
          {
            LedOff((int) g_vibrateIndex);
          }

          // Increment the count
          g_notifyCount ++;
        }

        // Notify that we need to reset the event timer loop
        triggered = TRUE;
      }
    }
  }
  while (g_hNotify || g_hEvent);

  return TRUE;  // Teriminating early...
}


void LoadRegistrySettings()
{
  HKEY hk;
  DWORD disp;
  DWORD maxBytes;
  RegCreateKeyEx(HKEY_CURRENT_USER, REGISTRY_SETTINGS, 0, _T(""), 0, 0, NULL, &hk, &disp);

  // Interval (in minutes) of notification
  maxBytes = sizeof(g_notifyInterval);
  if (RegQueryValueEx(hk, _T("NotifyInterval"), 0, NULL, (LPBYTE) &g_notifyInterval, &maxBytes))
  {
    g_notifyInterval = 15;
    RegSetValueEx(hk, _T("NotifyInterval"), 0, REG_DWORD, (LPBYTE) &g_notifyInterval, maxBytes);
  }

  // Use inactive hours to block notifications during certain times
  maxBytes = sizeof(g_useInactiveTime);
  if (RegQueryValueEx(hk, _T("UseInactiveTime"), 0, NULL, (LPBYTE) &g_useInactiveTime, &maxBytes))
  {
    g_useInactiveTime = 0;
    RegSetValueEx(hk, _T("UseInactiveTime"), 0, REG_DWORD, (LPBYTE) &g_useInactiveTime, maxBytes);
  }

  // Inactive hour start
  maxBytes = sizeof(g_hourInactiveStart);
  if (RegQueryValueEx(hk, _T("HourInactiveStart"), 0, NULL, (LPBYTE) &g_hourInactiveStart, &maxBytes))
  {
    g_hourInactiveStart = 0;
    RegSetValueEx(hk, _T("HourInactiveStart"), 0, REG_DWORD, (LPBYTE) &g_hourInactiveStart, maxBytes);
  }

  // Inactive hour start
  maxBytes = sizeof(g_hourInactiveEnd);
  if (RegQueryValueEx(hk, _T("HourInactiveEnd"), 0, NULL, (LPBYTE) &g_hourInactiveEnd, &maxBytes))
  {
    g_hourInactiveEnd = 0;
    RegSetValueEx(hk, _T("HourInactiveEnd"), 0, REG_DWORD, (LPBYTE) &g_hourInactiveEnd, maxBytes);
  }

  // Notify when reminders are active
  maxBytes = sizeof(g_notifyReminder);
  if (RegQueryValueEx(hk, _T("NotifyReminder"), 0, NULL, (LPBYTE) &g_notifyReminder, &maxBytes))
  {
    g_notifyReminder = 1;
    RegSetValueEx(hk, _T("NotifyReminder"), 0, REG_DWORD, (LPBYTE) &g_notifyReminder, maxBytes);
  }

  // Notify when missed calls are waiting
  maxBytes = sizeof(g_notifyMissedCall);
  if (RegQueryValueEx(hk, _T("NotifyMissedCall"), 0, NULL, (LPBYTE) &g_notifyMissedCall, &maxBytes))
  {
    g_notifyMissedCall = 1;
    RegSetValueEx(hk, _T("NotifyMissedCall"), 0, REG_DWORD, (LPBYTE) &g_notifyMissedCall, maxBytes);
  }

  // Notify when missed calls are waiting
  maxBytes = sizeof(g_notifyVoiceMail);
  if (RegQueryValueEx(hk, _T("NotifyVoiceMail"), 0, NULL, (LPBYTE) &g_notifyVoiceMail, &maxBytes))
  {
    g_notifyVoiceMail = 1;
    RegSetValueEx(hk, _T("NotifyVoiceMail"), 0, REG_DWORD, (LPBYTE) &g_notifyVoiceMail, maxBytes);
  }

  // Notify when there are unread SMS
  maxBytes = sizeof(g_notifySMS);
  if (RegQueryValueEx(hk, _T("NotifySMS"), 0, NULL, (LPBYTE) &g_notifySMS, &maxBytes))
  {
    g_notifySMS = 1;
    RegSetValueEx(hk, _T("NotifySMS"), 0, REG_DWORD, (LPBYTE) &g_notifySMS, maxBytes);
  }

  // Notify when there are unread MMS
  maxBytes = sizeof(g_notifyMMS);
  if (RegQueryValueEx(hk, _T("NotifyMMS"), 0, NULL, (LPBYTE) &g_notifyMMS, &maxBytes))
  {
    g_notifyMMS = 1;
    RegSetValueEx(hk, _T("NotifyMMS"), 0, REG_DWORD, (LPBYTE) &g_notifyMMS, maxBytes);
  }

  // Notify when there are unread email
  maxBytes = sizeof(g_notifyEmail);
  if (RegQueryValueEx(hk, _T("NotifyEmail"), 0, NULL, (LPBYTE) &g_notifyEmail, &maxBytes))
  {
    g_notifyEmail = 1;
    RegSetValueEx(hk, _T("NotifyEmail"), 0, REG_DWORD, (LPBYTE) &g_notifyEmail, maxBytes);
  }

  // Notify when device is powered on?
  maxBytes = sizeof(g_notifyWhileOn);
  if (RegQueryValueEx(hk, _T("NotifyWhileOn"), 0, NULL, (LPBYTE) &g_notifyWhileOn, &maxBytes))
  {
    g_notifyWhileOn = 0;
    RegSetValueEx(hk, _T("NotifyWhileOn"), 0, REG_DWORD, (LPBYTE) &g_notifyWhileOn, maxBytes);
  }

  // Power on before playing?
  maxBytes = sizeof(g_powerOnBeforePlay);
  if (RegQueryValueEx(hk, _T("PowerOnBeforePlay"), 0, NULL, (LPBYTE) &g_powerOnBeforePlay, &maxBytes))
  {
    g_powerOnBeforePlay = 0;
    RegSetValueEx(hk, _T("PowerOnBeforePlay"), 0, REG_DWORD, (LPBYTE) &g_powerOnBeforePlay, maxBytes);
  }

  // Stop notifying for a stale reminder after a certain number of reminders?
  maxBytes = sizeof(g_stopNotifyAfter);
  if (RegQueryValueEx(hk, _T("StopNotifyAfter"), 0, NULL, (LPBYTE) &g_stopNotifyAfter, &maxBytes))
  {
    g_stopNotifyAfter = 0;
    RegSetValueEx(hk, _T("StopNotifyAfter"), 0, REG_DWORD, (LPBYTE) &g_stopNotifyAfter, maxBytes);
  }

  // Should we vibrate?
  maxBytes = sizeof(g_vibrate);
  if (RegQueryValueEx(hk, _T("Vibrate"), 0, NULL, (LPBYTE) &g_vibrate, &maxBytes))
  {
    g_vibrate = 0;
    RegSetValueEx(hk, _T("Vibrate"), 0, REG_DWORD, (LPBYTE) &g_vibrate, maxBytes);
  }

  // Do the all-day workaround hack?
  maxBytes = sizeof(g_allDayWorkaroundHack);
  if (RegQueryValueEx(hk, _T("AllDayWorkaroundHack"), 0, NULL, (LPBYTE) &g_allDayWorkaroundHack, &maxBytes))
  {
    g_allDayWorkaroundHack = 0;
    RegSetValueEx(hk, _T("AllDayWorkaroundHack"), 0, REG_DWORD, (LPBYTE) &g_allDayWorkaroundHack, maxBytes);
  }

  // What is the vibration index?
  maxBytes = sizeof(g_vibrateIndex);
  if (RegQueryValueEx(hk, _T("VibrateLEDIndex"), 0, NULL, (LPBYTE) &g_vibrateIndex, &maxBytes))
  {
    g_vibrateIndex = 1;
    RegSetValueEx(hk, _T("VibrateLEDIndex"), 0, REG_DWORD, (LPBYTE) &g_vibrateIndex, maxBytes);
  }

  // Sound file!
  maxBytes = MAX_LOADSTRING * sizeof(wchar_t);
  if (RegQueryValueEx(hk, _T("SoundFile"), 0, NULL, (LPBYTE) g_soundFile, &maxBytes))
  {
    _tcscpy(g_soundFile, _T("\\Program Files\\ReRemind\\crystal.wav"));
    RegSetValueEx(hk, _T("SoundFile"), 0, REG_SZ, (LPBYTE) g_soundFile, maxBytes);
  }

  RegCloseKey(hk);
}


void AddMinutes(SYSTEMTIME *time, int minutes)
{
  FILETIME ft;
  ULARGE_INTEGER ui;

  __int64 delay = 10;  // 1 microsecond
  delay *= 1000;       // 1 millisecond
  delay *= 1000;       // 1 second
  delay *= 60;         // 1 minute
  delay *= minutes;

  if (SystemTimeToFileTime(time, &ft))
  {
    ui.HighPart = ft.dwHighDateTime;
    ui.LowPart = ft.dwLowDateTime;

    ui.QuadPart += delay;

    ft.dwHighDateTime = ui.HighPart;
    ft.dwLowDateTime = ui.LowPart;

    FileTimeToSystemTime(&ft, time);
  }
}

BOOL IsNotificationReady()
{
  // Set the idle timer (just in case)
  SystemIdleTimerReset();

  // static variables storing the last state of each notification
  static DWORD lastReminderActive = 0;
  static DWORD lastMissCallActive = 0;
  static DWORD lastVoiceMailActive = 0;
  static DWORD lastSMSActive = 0;
  static DWORD lastMMSActive = 0;
  static DWORD lastEmailActive = 0;

  // variables storing the current state of each notification
  DWORD reminderActivea = 0;
  DWORD reminderActiveb = 0;
  DWORD reminderActivec = 0;
  DWORD missCallActive = 0;
  DWORD voiceMailActive = 0;
  DWORD SMSActive = 0;
  DWORD MMSActive = 0;
  DWORD emailActive = 0;

  // other variables
  HKEY hk;
  DWORD temp = 0;
  DWORD maxBytes;

  // Check reminders
  if (g_notifyReminder)  
  {
    if (!IsWindows65())
    {
      if (RegOpenKeyEx(HKEY_CURRENT_USER, REG_REMINDERKEY, 0, 0, &hk) == ERROR_SUCCESS)
      {
        // Count the number of reminders based on the number of subkeys
        RegQueryInfoKey(hk, NULL, NULL, NULL, &reminderActivea, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
        RegCloseKey(hk);
      }
    }
    else
    {
      if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REG_REMINDERKEY65, 0, 0, &hk) == ERROR_SUCCESS)
      {
        maxBytes = sizeof(DWORD);
        RegQueryValueEx(hk, REG_REMINDERVALUE65a, 0, NULL, (LPBYTE) &reminderActivea, &maxBytes);
        RegQueryValueEx(hk, REG_REMINDERVALUE65b, 0, NULL, (LPBYTE) &reminderActiveb, &maxBytes);
        RegQueryValueEx(hk, REG_REMINDERVALUE65c, 0, NULL, (LPBYTE) &reminderActivec, &maxBytes);
        RegCloseKey(hk);

        reminderActivea += reminderActiveb + reminderActivec;
      }
    }
  }

  // Check missed calls
  if (g_notifyMissedCall)
  {
    if (RegOpenKeyEx(HKEY_CURRENT_USER, REG_MISSEDCALLKEY, 0, 0, &hk) == ERROR_SUCCESS)
    {
      maxBytes = sizeof(DWORD);
      RegQueryValueEx(hk, REG_MISSEDCALLVALUE, 0, NULL, (LPBYTE) &missCallActive, &maxBytes);
      RegCloseKey(hk);
    }
  }

  // Check voicemails
  if (g_notifyVoiceMail)
  {
    if (RegOpenKeyEx(HKEY_CURRENT_USER, REG_VOICEMAILKEY, 0, 0, &hk) == ERROR_SUCCESS)
    {
      maxBytes = sizeof(DWORD);
      RegQueryValueEx(hk, REG_VOICEMAILVALUE, 0, NULL, (LPBYTE) &voiceMailActive, &maxBytes);
      RegCloseKey(hk);
    }
  }

  // Check SMSes
  if (g_notifySMS)
  {
    if (RegOpenKeyEx(HKEY_CURRENT_USER, REG_SMSKEY, 0, 0, &hk) == ERROR_SUCCESS)
    {
      maxBytes = sizeof(DWORD);
      RegQueryValueEx(hk, REG_SMSVALUE, 0, NULL, (LPBYTE) &SMSActive, &maxBytes);
      RegCloseKey(hk);
    }
  }

  // Check MMSes
  if (g_notifyMMS)
  {
    if (RegOpenKeyEx(HKEY_CURRENT_USER, REG_MMSKEY, 0, 0, &hk) == ERROR_SUCCESS)
    {
      maxBytes = sizeof(DWORD);
      RegQueryValueEx(hk, REG_MMSVALUE, 0, NULL, (LPBYTE) &MMSActive, &maxBytes);
      RegCloseKey(hk);
    }
  }

  // Check emails
  if (g_notifyEmail)
  {
    if (RegOpenKeyEx(HKEY_CURRENT_USER, REG_EMAILKEY, 0, 0, &hk) == ERROR_SUCCESS)
    {
      maxBytes = sizeof(DWORD);
      RegQueryValueEx(hk, REG_EMAILVALUE, 0, NULL, (LPBYTE) &emailActive, &maxBytes);
      RegCloseKey(hk);
    }
  }

  // If the counts increase on any item, reset the global count
  if ( (reminderActivea > lastReminderActive) ||
       (missCallActive > lastMissCallActive) ||
       (voiceMailActive > lastVoiceMailActive) ||
       (SMSActive > lastSMSActive) ||
       (MMSActive > lastMMSActive) ||
       (emailActive > lastEmailActive) )
  {
    g_notifyCount = 0;
  }

  // Assign the "last" values
  lastReminderActive = reminderActivea;
  lastMissCallActive = missCallActive;
  lastVoiceMailActive = voiceMailActive;
  lastSMSActive = SMSActive;
  lastMMSActive = MMSActive;
  lastEmailActive = emailActive;

  // If any count is nonzero, return true
  if (reminderActivea || missCallActive || voiceMailActive || SMSActive || MMSActive || emailActive)
  {
    return TRUE;
  }

  return FALSE;
}

BOOL IsNotificationAllowed()
{
  // Set the idle timer (just in case)
  SystemIdleTimerReset();

  // Do we care what the power state is and also what is the current power state?
  if (!g_notifyWhileOn && IsPowerStateOn())
  {
    // The device is on, so return FALSE as it is not okay to sound the alarm!
    return FALSE;
  }

  // Do we have hour restrictions?
  if (g_useInactiveTime)
  {
    // Fetch the current time
    SYSTEMTIME curSysTime;
    GetLocalTime(&curSysTime);

    // Case: hour_start < hour_end - easy math!
    if (g_hourInactiveStart < g_hourInactiveEnd)
    {
      if (curSysTime.wHour >= g_hourInactiveStart && curSysTime.wHour < g_hourInactiveEnd)
      {
        return FALSE;
      }
    }
    // Case: hour_start > hour_end - harder math!
    else if (g_hourInactiveStart > g_hourInactiveEnd)
    {
      if (curSysTime.wHour >= g_hourInactiveStart || curSysTime.wHour < g_hourInactiveEnd)
      {
        return FALSE;
      }
    }
    // if start == end, this doesn't make sense, so it's as if useInactiveTime is off
  }

  // Do we have a limit of how many reminders can be sounded?
  if (g_stopNotifyAfter > 0)
  {
    if (g_notifyCount >= g_stopNotifyAfter)
    {
      return FALSE;
    }
  }

  return TRUE;
}

BOOL IsPowerStateOn()
{
  wchar_t pBuffer[MAX_STATE_NAMEL];
  DWORD pFlags = 0;

  // Fetch the current power state
  if (GetSystemPowerState(pBuffer, MAX_STATE_NAMEL, &pFlags) == ERROR_SUCCESS)
  {
    // What is the current power state?
    if (pFlags & POWER_STATE_ON)
    {
      // The device is on!
      return TRUE;
    }
  }

  return FALSE;
}

void NotificationCallBack(HREGNOTIFY hNotify, DWORD dwUserData, const PBYTE pData, const UINT cbData)
{
  SYSTEMTIME curSysTime;

  // Check to see if we need to schedule an event
  if (IsNotificationReady())
  {
    // Check to see that there is no notification currently scheduled
    if (!g_hNotify)
    {
      // Set the next event trigger
      GetLocalTime(&curSysTime);
      AddMinutes(&curSysTime, g_notifyInterval);
      g_trigger.stStartTime = curSysTime;
      g_trigger.stEndTime = curSysTime;

      // Set notification for event
      g_hNotify = CeSetUserNotificationEx(NULL, &g_trigger, NULL);
    }
  }
  else  // clear out any existing notification
  {
    if (g_hNotify)
    {
      CeClearUserNotification(g_hNotify);
      g_hNotify = NULL;
    }
  }

  // Do the all day reminder hack (it checks if we should do it or not in the function)
  DoAllDayWorkaroundHack();
}

void DestroyReRemindNotifications()
{
  UserNotificationTrigger unt = {0}; 
  DWORD dwHowMany = 0;
  DWORD dwBytesNeeded = 0; 
  CE_NOTIFICATION_INFO_HEADER *notify = NULL; 
  LPBYTE lpBuffer = NULL;
  HANDLE* pHandles = NULL; 

  if(CeGetUserNotificationHandles(NULL, 0, &dwHowMany)) 
  { 
    if(dwHowMany > 0) 
    { 
      pHandles = new HANDLE[dwHowMany]; 

      if(pHandles != NULL) 
      { 
        if(CeGetUserNotificationHandles(pHandles, dwHowMany, &dwHowMany)) 
        { 
          for(DWORD i = 0; i < dwHowMany; i++) 
          { 
            CeGetUserNotification(pHandles[i], 0, &dwBytesNeeded, NULL); 

            lpBuffer = NULL; 
            lpBuffer = new BYTE[dwBytesNeeded]; 

            if(lpBuffer != NULL) 
            { 
              memset(lpBuffer, 0, dwBytesNeeded); 

              if(CeGetUserNotification(pHandles[i], dwBytesNeeded, &dwBytesNeeded, lpBuffer)) 
              { 
                notify = (CE_NOTIFICATION_INFO_HEADER*) lpBuffer; 

                if(notify->pcent->lpszApplication) 
                {                         
                  if(!_tcsicmp(notify->pcent->lpszApplication, g_eventPath)) 
                  {       
                    CeClearUserNotification(pHandles[i]);
                  }    
                } 
              } 

              delete[] lpBuffer; 
            } 
          }  
        } 

        delete[] pHandles; 
      } 
    } 
  }
}

BOOL IsWindows65()
{
  HKEY hk;
  DWORD disp;
  DWORD maxBytes;
  wchar_t akuver[MAX_LOADSTRING] = {0};
  static BOOL onceThrough = FALSE;
  static BOOL W65 = FALSE;

  if (!onceThrough)
  {
    // Check the AKU
    RegCreateKeyEx(HKEY_LOCAL_MACHINE, REG_AKUKEY, 0, _T(""), 0, 0, NULL, &hk, &disp);
    maxBytes = MAX_LOADSTRING * sizeof(wchar_t);
    if (RegQueryValueEx(hk, REG_AKUVALUE, 0, NULL, (LPBYTE) &akuver, &maxBytes))
    {
      W65 = FALSE;
    }
    else
    {
      wchar_t verc;
      if (akuver[0] == _T('.') && akuver[2] == _T('.'))
      {
        verc = akuver[1];
      }
      else if (akuver[1] == _T('.'))
      {
        verc = akuver[0];
      }
      else
      {
        verc = _T('0');
      }

      if (verc == _T('0') || verc == _T('1') || verc == _T('2') || verc == _T('3') || verc == _T('4'))
      {
        W65 = FALSE;
      }
      else
      {
        W65 = TRUE;
      }
    }

    onceThrough = TRUE;
  }

  return W65;
}


void DoAllDayWorkaroundHack()
{
  HKEY hk;
  DWORD maxBytes;
  
  if (g_allDayWorkaroundHack && IsWindows65())
  {
    DWORD allDayCount = 0;

    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REG_REMINDERKEY65, 0, 0, &hk) == ERROR_SUCCESS)
    {
      maxBytes = sizeof(DWORD);
      RegQueryValueEx(hk, REG_REMINDERVALUE65b, 0, NULL, (LPBYTE) &allDayCount, &maxBytes);
      RegCloseKey(hk);
    }
 
    if (allDayCount > g_lastAllDayCount)
    {
      DWORD options = 0;
      DWORD maxBytes;
      wchar_t wavFile[MAX_LOADSTRING];

      if (RegOpenKeyEx(HKEY_CURRENT_USER, REG_REMINDEROPTIONSKEY, 0, 0, &hk) == ERROR_SUCCESS)
      {
        // Look to see if we need to chime or vibrate
        maxBytes = sizeof(DWORD);
        RegQueryValueEx(hk, REG_REMINDEROPTIONSVALUE, 0, NULL, (LPBYTE) &options, &maxBytes);

        // Fetch the reminder sound
        maxBytes = MAX_LOADSTRING * sizeof(wchar_t);
        RegQueryValueEx(hk, REG_REMINDERSOUNDVALUE, 0, NULL, (LPBYTE) wavFile, &maxBytes);

        // close the opened registry key
        RegCloseKey(hk);

        // It should already be awake at this point, but just in case...
        SetSystemPowerState(NULL, POWER_STATE_ON, 0);

        // bit 1 = play sound
        // bit 2 = vibrate
        if (options & 0x2)
        {
          LedOn((int) g_vibrateIndex);
        }

        if (options & 0x1)
        {
          SndPlaySync(wavFile, 0);
        }
        else if (options & 0x2) // No sound, so vibrate for two seconds if vibrating
        {
          Sleep(2000);
        }

        LedOff((int) g_vibrateIndex);
      }
    }

    g_lastAllDayCount = allDayCount;
  }
}